package pl.polsl.mmm.projectcomm;

import static android.opengl.GLES20.*;
import static android.opengl.Matrix.*;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;

import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import pl.polsl.mmm.projectcomm.data.Color;
import pl.polsl.mmm.projectcomm.data.vec2;
import pl.polsl.mmm.projectcomm.data.vec3;
import pl.polsl.mmm.projectcomm.objects.Background;
import pl.polsl.mmm.projectcomm.objects.LightSource;
import pl.polsl.mmm.projectcomm.objects.Lines;
import pl.polsl.mmm.projectcomm.objects.MD5.MD5;
import pl.polsl.mmm.projectcomm.objects.MD5.MD5Action;
import pl.polsl.mmm.projectcomm.objects.ModelBob;
import pl.polsl.mmm.projectcomm.objects.Platform;
import pl.polsl.mmm.projectcomm.objects.PlatformBuldier;
import pl.polsl.mmm.projectcomm.objects.state.BobState;
import pl.polsl.mmm.projectcomm.objects.state.MD5ActionState;
import pl.polsl.mmm.projectcomm.programs.BackgroundShaderProgram;
import pl.polsl.mmm.projectcomm.programs.ColorShaderProgram;
import pl.polsl.mmm.projectcomm.programs.TextureLightShaderProgram;
import pl.polsl.mmm.projectcomm.programs.TextureShaderProgram;

import static pl.polsl.mmm.projectcomm.Constants.MD5_METHOD_LERP;
import static pl.polsl.mmm.projectcomm.util.Geometry.*;

import pl.polsl.mmm.projectcomm.util.Geometry;
import pl.polsl.mmm.projectcomm.util.MatrixHelper;

public class GameRenderer implements Renderer {
	private final Context context;
	private final float[] projectionMatrix = new float[16];
	private final float[] modelMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private final float[] viewProjectionMatrix = new float[16];
    private final float[] modelViewProjectionMatrix = new float[16];
	private final float[] invertedViewProjectionMatrix = new float[16];

    private ColorShaderProgram colorProgram;
	//private TextureShaderProgram textureProgram;
    private TextureLightShaderProgram textureLightProgram;
    private BackgroundShaderProgram backgroundProgram;
	//private ColorShaderProgram colorProgram;
    //stopień wychylenia joysticka w poziomie
    private int pan;
    //stopień wychylenia joysticka w pionie
    private int tilt;
    //stan przycisku skoku
    private boolean jump;
    // stan przycisków gestów
    private Constants.Gestures gestureState;

    private ModelBob bob;
	private ModelBob bob2;
    private Background background;

    private int screenWidth;
    private int screenHight;

    //źródło światła
    private LightSource lightSource;

    // Fizyka - świat
    private World world = new World(new Vec2(0f, -25f), false);

    // Platformy
    public ArrayList<Platform> platforms = new ArrayList<Platform>();

   // private Lines touchLine;

    private float bobVelocityY = 0f;

	public GameRenderer(Context context ) {
		this.context = context;
        gestureState = Constants.Gestures.NO_GESTURES;
	}

	private void positionModelInScene(ModelBob model) {
		multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, model.getModelMatrix(), 0);
	}

    private void positionBackgroundInScene() {
        setIdentityM(modelMatrix, 0);
        translateM(modelMatrix, 0, 0f, 0f, -20f);
        multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
    }

	@Override
	public void onSurfaceChanged(GL10 arg0, int width, int height) {
        screenWidth = width;
        screenHight = height;
		glViewport (0,0, width, height);

        MatrixHelper.perspectiveM(projectionMatrix, 45f, (float) width / (float) height, 0.1f, 60f);
        setLookAtM(viewMatrix, 0, 0f, 0f, 1f, 0f, 0f, 0f ,0f ,1f, 0f);
	}

	@Override
	public void onDrawFrame(GL10 gl) {

        // Krok symulacji fizyki
//        world.step(1/60f, 8, 3);
        world.step(1/20f, 8, 3);
        glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);


        /**RYSUJEMY NAJPIERW DRUGIEGO BOBA (Ekran u góry) */
        glViewport (0, screenHight/2, screenWidth, screenHight/2);
        // Przesuń kamerę za bobem 2
        setLookAtM(viewMatrix, 0,
                bob2.getLocation().x / 2, bob2.getLocation().y + 4.5f, 1.0f, // lokalizacja kamery
                bob2.getLocation().x / 2, bob2.getLocation().y + 4.5f, -15.f, // punkt na który patrzy kamera
                0.0f, 1.0f, 0.0f); // wektor pionu

        multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        invertM(invertedViewProjectionMatrix, 0, viewProjectionMatrix, 0);


        /**Najpierw rysujemy background*/
        positionBackgroundInScene();
        backgroundProgram.useProgram();
        backgroundProgram.setMatrix(modelViewProjectionMatrix);
        background.bindData(backgroundProgram);
        background.draw();
        /** Potem platformy*/
        for (Platform platform : platforms) {
            // Na drugim ekranie rysujemy wszystkie platformy (także te ukryte)
            platform.bindData(backgroundProgram);
            platform.draw();
        }
        /** Na końcu postać */
        positionModelInScene(bob2);
        textureLightProgram.useProgram();
        textureLightProgram.setMatrices(modelViewProjectionMatrix,viewMatrix);
        textureLightProgram.setLight(lightSource);
        //textureProgram.useProgram();
        //textureProgram.setMatrix(modelViewProjectionMatrix);
        bob2.animateBob2();
        bob2.drawModel();

        /** Rysujemy ekran dolny (gracza sterującego) */
        glViewport(0, 0, screenWidth, screenHight / 2);

        if (gestureState == Constants.Gestures.NO_GESTURES){
            if (this.jump){
                bob.go(pan);
                bob.setModelState(Constants.Model_state.JUMP);

                //TODO
                //skok do góry
               /* if (bob.getVelocityY() == 0){
                    Log.d("Prędkość w Y", "Szczyt skoku");
                }*/
                if (bob.getVelocityY() == 0 && bobVelocityY == 0 ) {
                    this.jump = false;
                 //   Log.d("Prędkość w Y", "Koniec skoku");
                }

                bobVelocityY = bob.getVelocityY();
            } else {
                // Jeśli joystick wychylony w bok
                if (this.pan != 0) {
                    // Przesuń boba w prawo/lewo
                    bob.go(pan);
                    bob.setModelState(Constants.Model_state.WALK);
                } else {
                    bob.setModelState(Constants.Model_state.IDLE);
                }
            }
        }

        /// Przesuń kamerę za bobem
        setLookAtM(viewMatrix, 0,
                bob.getLocation().x / 2, bob.getLocation().y + 4.5f, 1.0f, // lokalizacja kamery
                bob.getLocation().x / 2, bob.getLocation().y + 4.5f, -15.f, // punkt na który patrzy kamera
                0.0f, 1.0f, 0.0f); // wektor pionu

        multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        invertM(invertedViewProjectionMatrix, 0, viewProjectionMatrix, 0);

        positionBackgroundInScene();
        backgroundProgram.useProgram();
        backgroundProgram.setMatrix(modelViewProjectionMatrix);
        background.bindData(backgroundProgram);
        background.draw();

        // Rysujemy platformy
        for (Platform platform : platforms) {
            // Sprawdzamy czy platforma jest ukryta tylko na naszym ekranie
            if (!platform.isHidden()) {
                platform.bindData(backgroundProgram);
                platform.draw();
            }
        }

        positionModelInScene(bob);
        textureLightProgram.useProgram();
        textureLightProgram.setMatrices(modelViewProjectionMatrix,viewMatrix);
        textureLightProgram.setLight(lightSource);
        //textureProgram.useProgram();
        //textureProgram.setMatrix(modelViewProjectionMatrix);
        bob.animate((float) pan / 10f);
        bob.drawModel();

        /**RYSOWANIE NORMALI */
        /**DEBUG ON!*/
       /* colorProgram.useProgram();
        //ustawienie macierzy dla linii
        colorProgram.setMatrix(modelViewProjectionMatrix);
        //bob.drawNormals();
        glDisable(GL_DEPTH_TEST);
        bob.drawSkeleton();
        //rysowanie linii
        touchLine.bindData(colorProgram);
        colorProgram.setColor(1f,0f,0f);
        touchLine.drawLines();
        colorProgram.setColor(1f,0f,1f);
        touchLine.drawPoints();
        glEnable(GL_DEPTH_TEST);*/
        /** DEBUG OFF! */


        /** Przesłanie danych do drugiego urządzenia*/
        GameActivity gameActivity = (GameActivity)this.context;
        gameActivity.sendBob(bob);

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        //glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
        //textureProgram = new TextureShaderProgram(context);
        textureLightProgram = new TextureLightShaderProgram(context);
        backgroundProgram = new BackgroundShaderProgram(context);
        colorProgram = new ColorShaderProgram(context);
        //modelOBJ = OBJParser.readOBJFileFromResource(context, R.raw.wolf2);
        //texture = TextureHelper.loadTexture(context, R.drawable.wolf_tex);
        //modelOBJ.bindData(textureProgram);

        //światło
        vec3 lightPos = new vec3(20.0f,-20.0f,50.0f);
        Color ambient = new Color(0.1f,0.1f,0.05f,0.25f);
        Color diffuse = new Color(0.3f,0.3f,0.3f,1.0f);
        Color specular = new Color(0.2f,0.2f,0.2f,1.0f);
        lightSource = new LightSource(lightPos,ambient,diffuse,specular);

        bob = new ModelBob(context, textureLightProgram, colorProgram, world, true);

        bob.basicMatrix();
        bob.loadActions(context);
        /**DEBUG ON*/
        //włączenie debugu normali
        //bob.debugNormalsON();
        //włączenie debugu kości
        //bob.debugSkeletonON();
        /** DEBUG OFF*/
        bob2 = new ModelBob(context, textureLightProgram, colorProgram, null, false);
        bob2.basicMatrix();
        bob2.loadActions(context);
        background = new Background(context);

        glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);

        // Fizyka - utworzenie obiektów fizycznych
        setUpObjects();
        /*
        touchLine = new Lines(2);
        touchLine.putBonesData(new vec3(0,0,0),new vec3(0,0,0));
        touchLine.putBonesData(new vec3(0,0,0),new vec3(0,0,0));*/
    }

    private void setUpObjects() {
        PlatformBuldier platformBuldier = new PlatformBuldier(platforms, world, context);
        // Tworzenie platform
        platformBuldier.setUpPlatforms();
        platformBuldier = null;
    }

    public void handleTouchPress(float normalizedX, float normalizedY) {
        if(gestureState == Constants.Gestures.ONE_HAND || gestureState == Constants.Gestures.TWO_HANDS) {
            Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);
            //obrót Boba w stronę kamery
            bob.basicMatrix();
        }
        //Log.d("Touch", "X: " + normalizedX + " Y: " + normalizedY);
    }

    public void handleTouchDrag(float normalizedX, float normalizedY) {
        if(gestureState == Constants.Gestures.ONE_HAND || gestureState == Constants.Gestures.TWO_HANDS) {
            Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);
            vec3 originBoneLocation = bob.getOriginBoneLocation();
            Plane plane = new Plane (new Point(originBoneLocation.x,originBoneLocation.y,0), new Vector(0,0,1) );
            vec3 touchedPoint = Geometry.intersectionPoint(ray,plane);
            touchedPoint.x = (touchedPoint.x - (bob.getLocation().x / 2)) * 31.5f;
            touchedPoint.z = (touchedPoint.y - bob.getLocation().y - 4.5f)* 30f + 10.5f;
            touchedPoint.y = originBoneLocation.y;
            /*
            touchLine.reset();
            touchLine.putBonesData( bob.moveHandsPose[16].getLocation(), touchedPoint);
            touchLine.putBonesData( bob.moveHandsPose[8].getLocation(),
                    new vec3(-touchedPoint.x,touchedPoint.y,touchedPoint.z) );
                    */
            if(gestureState == Constants.Gestures.ONE_HAND)
                bob.animateHand(touchedPoint, true);

            else {
                bob.animateHand(new vec3(-touchedPoint.x,touchedPoint.y,touchedPoint.z), false);
                bob.animateHand(touchedPoint, true);
            }
            bob.setModelState(Constants.Model_state.PUPPET_STATE);
            bob.setGesture(gestureState, touchedPoint);
            //obrót Boba w stronę kamery
            bob.basicMatrix();
        }
        //Log.d("Drag", "X: " + normalizedX + "   Y: " + normalizedY);
    }

    private float clamp(float value, float min, float max) {
        return Math.min(max,  Math.max(value, min));
    }

    private Ray convertNormalized2DPointToRay(float normalizedX, float normalizedY) {
        //konwersja znormalizowanych wspolrzednych urzadzenia do wspolrzednych swiata. Wybor punktu na planie bliskim i dalekim frusty
        //rysowanie linii miedzy nimi. Do tego wymagane jest mnozenie przez odwrocona macierz ViewProjection
        //i cofniecie dzielenia perspektywicznego ( przez w )
        final float[] nearPointNdc = {normalizedX, normalizedY, -1, 1};
        final float[] farPointNdc = {normalizedX, normalizedY, 1, 1};

        final float[] nearPointWorld = new float[4];
        final float[] farPointWorld = new float[4];

        multiplyMV(nearPointWorld, 0, invertedViewProjectionMatrix, 0, nearPointNdc, 0);
        multiplyMV(farPointWorld, 0, invertedViewProjectionMatrix, 0, farPointNdc, 0);

        divideByW(nearPointWorld);
        divideByW(farPointWorld);

        Point nearPointRay = new Point(nearPointWorld[0], nearPointWorld[1], nearPointWorld[2]);
        Point farPointRay = new Point(farPointWorld[0], farPointWorld[1], farPointWorld[2]);

        return new Ray(nearPointRay,
                vectorBetween(nearPointRay, farPointRay));
    }

    private void divideByW(float[] vector) {
        vector[0] /= vector[3];
        vector[1] /= vector[3];
        vector[2] /= vector[3];
    }

    public void setPan(int pan) {
        this.pan = pan;
    }

    public void setTilt(int tilt) {
        this.tilt = tilt;
    }

    // Skok postaci
    public void startJump() {
        this.jump = true;
        bob.jump();
    }


    public void setGestureState(Constants.Gestures state) {
        this.gestureState = state;

        // Log start
        String gestureText = "";
        if (this.gestureState == Constants.Gestures.ONE_HAND) {
            bob.setModelState(Constants.Model_state.PUPPET_STATE);
            gestureText = "One hand in use";
        } else if (this.gestureState == Constants.Gestures.TWO_HANDS) {
            bob.setModelState(Constants.Model_state.PUPPET_STATE);
            gestureText = "Two hands in use";
        } else if (this.gestureState == Constants.Gestures.NO_GESTURES) {
            gestureText = "Without hands";
            bob.resetMoveHandsPose();
            bob.setGesture(Constants.Gestures.NO_GESTURES, new vec3(0,0,0));
        }
        Log.d("Gesture", gestureText);
        // Log end
    }

    // Przypisanie do boba2 danych odebranych przez bluetooth
    public void setBob2Params(BobState bobState) {

        if (bobState != null && bob2 != null) {

            bob2.setLocation(bobState.location);
            bob2.setRotation(bobState.rotation);
            bob2.setModelState(bobState.modelState);
            bob2.setSpeedRate(bobState.speedRate);
            bob2.setTouchedPoint(bobState.touchedPoint);
            bob2.setGest(bobState.gesture);

            ArrayList<MD5Action> actionList = bob2.getModel().md5action;

            if (actionList.size() == bobState.md5action.size()) {
                for (int i = 0; i < bobState.md5action.size(); i++) {

                    actionList.get(i).setCurr_frame(bobState.md5action.get(i).curr_frame);
                  //  actionList.get(i).setNext_frame(bobState.md5action.get(i).next_frame);
                    actionList.get(i).setState(bobState.md5action.get(i).state);
                }

                bob2.getModel().setMd5action(actionList);
            }
        }
    }

}
